// Copyright 2025 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package ginS

import (
	"html/template"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func init() {
	gin.SetMode(gin.TestMode)
}

func TestGET(t *testing.T) {
	GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "test")
	})

	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "test", w.Body.String())
}

func TestPOST(t *testing.T) {
	POST("/post", func(c *gin.Context) {
		c.String(http.StatusCreated, "created")
	})

	req := httptest.NewRequest(http.MethodPost, "/post", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	assert.Equal(t, "created", w.Body.String())
}

func TestPUT(t *testing.T) {
	PUT("/put", func(c *gin.Context) {
		c.String(http.StatusOK, "updated")
	})

	req := httptest.NewRequest(http.MethodPut, "/put", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "updated", w.Body.String())
}

func TestDELETE(t *testing.T) {
	DELETE("/delete", func(c *gin.Context) {
		c.String(http.StatusOK, "deleted")
	})

	req := httptest.NewRequest(http.MethodDelete, "/delete", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "deleted", w.Body.String())
}

func TestPATCH(t *testing.T) {
	PATCH("/patch", func(c *gin.Context) {
		c.String(http.StatusOK, "patched")
	})

	req := httptest.NewRequest(http.MethodPatch, "/patch", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "patched", w.Body.String())
}

func TestOPTIONS(t *testing.T) {
	OPTIONS("/options", func(c *gin.Context) {
		c.String(http.StatusOK, "options")
	})

	req := httptest.NewRequest(http.MethodOptions, "/options", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "options", w.Body.String())
}

func TestHEAD(t *testing.T) {
	HEAD("/head", func(c *gin.Context) {
		c.String(http.StatusOK, "head")
	})

	req := httptest.NewRequest(http.MethodHead, "/head", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestAny(t *testing.T) {
	Any("/any", func(c *gin.Context) {
		c.String(http.StatusOK, "any")
	})

	req := httptest.NewRequest(http.MethodGet, "/any", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "any", w.Body.String())
}

func TestHandle(t *testing.T) {
	Handle(http.MethodGet, "/handle", func(c *gin.Context) {
		c.String(http.StatusOK, "handle")
	})

	req := httptest.NewRequest(http.MethodGet, "/handle", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "handle", w.Body.String())
}

func TestGroup(t *testing.T) {
	group := Group("/group")
	group.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "group test")
	})

	req := httptest.NewRequest(http.MethodGet, "/group/test", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "group test", w.Body.String())
}

func TestUse(t *testing.T) {
	var middlewareExecuted bool
	Use(func(c *gin.Context) {
		middlewareExecuted = true
		c.Next()
	})

	GET("/middleware-test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest(http.MethodGet, "/middleware-test", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.True(t, middlewareExecuted)
	assert.Equal(t, http.StatusOK, w.Code)
}

func TestNoRoute(t *testing.T) {
	NoRoute(func(c *gin.Context) {
		c.String(http.StatusNotFound, "custom 404")
	})

	req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	assert.Equal(t, "custom 404", w.Body.String())
}

func TestNoMethod(t *testing.T) {
	NoMethod(func(c *gin.Context) {
		c.String(http.StatusMethodNotAllowed, "method not allowed")
	})

	// This just verifies that NoMethod is callable
	// Testing the actual behavior would require a separate engine instance
	assert.NotNil(t, engine())
}

func TestRoutes(t *testing.T) {
	GET("/routes-test", func(c *gin.Context) {})

	routes := Routes()
	assert.NotEmpty(t, routes)

	found := false
	for _, route := range routes {
		if route.Path == "/routes-test" && route.Method == http.MethodGet {
			found = true
			break
		}
	}
	assert.True(t, found)
}

func TestSetHTMLTemplate(t *testing.T) {
	tmpl := template.Must(template.New("test").Parse("Hello {{.}}"))
	SetHTMLTemplate(tmpl)

	// Verify engine has template set
	assert.NotNil(t, engine())
}

func TestStaticFile(t *testing.T) {
	StaticFile("/static-file", "../testdata/test_file.txt")

	req := httptest.NewRequest(http.MethodGet, "/static-file", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestStatic(t *testing.T) {
	Static("/static-dir", "../testdata")

	req := httptest.NewRequest(http.MethodGet, "/static-dir/test_file.txt", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestStaticFS(t *testing.T) {
	fs := http.Dir("../testdata")
	StaticFS("/static-fs", fs)

	req := httptest.NewRequest(http.MethodGet, "/static-fs/test_file.txt", nil)
	w := httptest.NewRecorder()
	engine().ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}
